/*************************************************************
 * Title: ClassHelper.java
 * Description: 
 * Author: Huang Shaobin
 * Email: shaobin.software@gmail.com
 * CreateTime: Feb 22, 2013 6:01:56 PM
 * Description:
 ************************************************************/
package snow.core.toolkit;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;

/**
 *
 */
public class ClassHelper {

	/** Prefix for jdk dynamic proxy class names: "$Proxy" */
	public static final String JDK_DYNAMIC_PROXY_CLASS_PREFIX = "$Proxy";

	/** Suffix for array class names: "[]" */
	public static final String ARRAY_SUFFIX = "[]";

	/** Prefix for internal array class names: "[" */
	public static final String INTERNAL_ARRAY_PREFIX = "[";

	/** Prefix for internal non-primitive array class names: "[L" */
	public static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";

	/** The package separator character '.' */
	public static final char PACKAGE_SEPARATOR = '.';

	/** The inner class separator character '$' */
	public static final char INNER_CLASS_SEPARATOR = '$';

	/** The CGLIB class separator character "$$" */
	public static final String CGLIB_CLASS_SEPARATOR = "$$EnhancerByCGLIB$$";

	/** The Javassist class separator character "$$" */
	public static final String JAVASSIST_CLASS_SEPARATOR = "_$$_javassist_";

	/** The ".class" file suffix */
	public static final String CLASS_FILE_SUFFIX = ".class";
	
	private static final Log log = LogFactory.getLog(ClassHelper.class);
	
	private static Map<Class<?>, Map<String, PropertyDescriptor>> beanPropertyDescriptorMap = new HashMap<Class<?>, Map<String, PropertyDescriptor>>();

	public static Class<?> getSubclassFrom(Class<?>[] classes, Class<?> superClass) {
		if (ArrayUtils.isEmpty(classes) || superClass == null) {
			return null;
		}
		for (final Class<?> subclass : classes) {
			if (subclass != null && superClass.isAssignableFrom(subclass)) {
				return subclass;
			}
		}
		return null;
	}
	
	/**
	 * 以文件的形式来获取包下的所有Class
	 * 
	 * @param packageName
	 * @param packagePath
	 * @param recursive
	 * @param classes
	 */
	public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
		// 获取此包的目录 建立一个File
		File dir = new File(packagePath);
		// 如果不存在或者 也不是目录就直接返回
		if (!dir.exists() || !dir.isDirectory()) {
			log.warn("用户定义包名 " + packageName + " 下没有任何文件!");
			return;
		}
		// 如果存在 就获取包下的所有文件 包括目录
		File[] dirfiles = dir.listFiles(new FileFilter() {
			// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
			public boolean accept(File file) {
				return recursive && file.isDirectory() || file.getName().toLowerCase().endsWith(".class");
			}
		});
		// 循环所有文件
		for (File file : dirfiles) {
			// 如果是目录 则继续扫描
			if (file.isDirectory()) {
				findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
			} else {
				// 如果是java类文件 去掉后面的.class 只留下类名
				String className = file.getName().substring(0, file.getName().length() - 6);
				try {
					// 添加到集合中去
					classes.add(Class.forName(packageName + '.' + className));
				} catch (ClassNotFoundException e) {
					log.error("添加用户自定义视图类错误 找不到此类的.class文件!", e);
				}
			}
		}
	}
	
	public static List<Field> getAllFieldsFrom(Class<?> candidate) {
		Assert.notNull(candidate, "Candidate object must not be null!");
		Field[] fields = candidate.getDeclaredFields();
		List<Field> list = new ArrayList<Field>(fields.length);
		for (Field field : fields) {
			list.add(list.size(), field);
		}
		Class<?> superClass = candidate.getSuperclass();
		if (!Object.class.equals(superClass)) {
			list.addAll(getAllFieldsFrom(superClass));
		}
		return list;
	}
	
	/**
	 * 获取指定包所有的类(包括所有下级包的类)
	 * 
	 * @param pack
	 * @return
	 */
	public static Set<Class<?>> getClassesFrom(Package pack) {
		// 第一个class类的集合
		Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
		// 是否循环迭代
		boolean recursive = true;
		// 获取包的名字 并进行替换
		String packageName = pack == null ? null : pack.getName();
		String packageDirName = StringUtils.replace(packageName, ".", "/");
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
			// 循环迭代下去
			while (dirs.hasMoreElements()) {
				// 获取下一个元素
				URL url = dirs.nextElement();
				// 得到协议的名称
				String protocol = url.getProtocol();
				// 如果是以文件的形式保存在服务器上
				if ("file".equals(protocol)) {
					// 获取包的物理路径
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					// 以文件的方式扫描整个包下的文件 并添加到集合中
					findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
				} else if ("jar".equals(protocol)) {
					// 如果是jar包文件
					// 定义一个JarFile
					JarFile jar;
					try {
						// 获取jar
						jar = ((JarURLConnection) url.openConnection()).getJarFile();
						// 从此jar包 得到一个枚举类
						Enumeration<JarEntry> entries = jar.entries();
						// 同样的进行循环迭代
						while (entries.hasMoreElements()) {
							// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
							JarEntry entry = entries.nextElement();
							String name = entry.getName();
							// 如果是以/开头的
							if (name.charAt(0) == '/') {
								// 获取后面的字符串
								name = name.substring(1);
							}
							// 如果前半部分和定义的包名相同
							if (name.startsWith(packageDirName)) {
								int idx = name.lastIndexOf('/');
								// 如果以"/"结尾 是一个包
								if (idx != -1) {
									// 获取包名 把"/"替换成"."
									packageName = name.substring(0, idx).replace('/', '.');
								}
								// 如果可以迭代下去 并且是一个包
								if (idx != -1 || recursive) {
									// 如果是一个.class文件 而且不是目录
									if (name.toLowerCase().endsWith(".class") && !entry.isDirectory()) {
										// 去掉后面的".class" 获取真正的类名
										String className = name.substring(packageName.length() + 1, name.length() - 6);
										try {
											// 添加到classes
											classes.add(Class.forName(packageName + '.' + className));
										} catch (ClassNotFoundException e) {
											log.error("添加用户自定义视图类错误 找不到此类的.class文件", e);
										}
									}
								}
							}
						}
					} catch (IOException e) {
						log.error("在扫描用户定义视图时从jar包获取文件出错!", e);
					}
				}
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		return classes;
	}
	
	/**
	 * 根据Bean的Class对象获取相应的PropertyDescriptor的Map集合,其中Map的key为PropertyDescriptor.getName()
	 * 
	 * @param clazz
	 *            Bean的Class对象
	 * @return
	 */
	public static Map<String, PropertyDescriptor> getPropertyDescriptorFrom(Class<?> clazz) {
		Map<String, PropertyDescriptor> map = beanPropertyDescriptorMap.get(clazz);
		if (map == null) {
			// 获取Bean的PropertyDescriptor对象数组
			PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz);
			// 创建Map对象
			map = new HashMap<String, PropertyDescriptor>(descriptors.length);
			// 迭代PropertyDescriptor对象数组
			for (PropertyDescriptor descriptor : descriptors) {
				// 获取get方法
				Method getterMethod = descriptor.getReadMethod();
				// 获取set方法
				Method setterMethod = descriptor.getWriteMethod();
				// 只有两个都不为空的情况才能将其放入Map集合中
				if (getterMethod != null && setterMethod != null) {
					map.put(descriptor.getName(), descriptor);
				}
			}
			beanPropertyDescriptorMap.put(clazz, map);
		}
		return map;
	}


	public static boolean isCglibProxy(Class<?> targetClass) {
		return StringUtils.contains((targetClass == null ? null : targetClass.getName()), CGLIB_CLASS_SEPARATOR);
	}

	public static boolean isDynamicProxy(Class<?> targetClass) {
		return isCglibProxy(targetClass) || isJavassistProxy(targetClass);
	}

	public static boolean isJavassistProxy(Class<?> targetClass) {
		return StringUtils.contains((targetClass == null ? null : targetClass.getName()), JAVASSIST_CLASS_SEPARATOR);
	}

	public static boolean isJdkDynamicProxy(Class<?> targetClass) {
		return StringUtils.contains((targetClass == null ? null : targetClass.getName()), JDK_DYNAMIC_PROXY_CLASS_PREFIX);
	}
}
